namespace Jsl.FxCop
{
    using System;
    using System.Collections;
    using System.Linq;
    using Microsoft.FxCop.Sdk;

    /// <summary>
    /// Utility methods to make working with Types and TypeNodes easier.
    /// </summary>
    public static class TypeNodeHelper
    {
        #region Public Methods
        /// <summary>
        /// Gets the type node.
        /// </summary>
        /// <param name="targetType">Type of the target.</param>
        /// <returns>The <see cref="TypeNode"/> found.</returns>
        public static TypeNode GetTypeNode(this Type targetType)
        {
            if (targetType == null)
            {
                throw new ArgumentNullException("targetType");
            }

            return
                targetType.Assembly.GetAssemblyNode()
                    .GetType(Identifier.For(targetType.Namespace), Identifier.For(targetType.Name));
        }

        /// <summary>
        /// Gets the type node.
        /// </summary>
        /// <param name="module">The module, usually the main assembly.</param>
        /// <param name="typeName">Name of the type.</param>
        /// <returns>The <see cref="TypeNode"/> found.</returns>
        public static TypeNode GetTypeNode(this ModuleNode module, string typeName)
        {
            if (module == null)
            {
                throw new ArgumentNullException("module");
            }

            if (string.IsNullOrEmpty(typeName))
            {
                throw new ArgumentNullException("typeName");
            }

            // Break up the parts of the full type name into the namespace and name.
            int lastDelimiter = typeName.LastIndexOf(Type.Delimiter);
            string @namespace;
            string name;
            if (lastDelimiter >= 0)
            {
                @namespace = typeName.Substring(0, lastDelimiter);
                name = typeName.Substring(lastDelimiter + 1);
            }
            else
            {
                @namespace = string.Empty;
                name = typeName;
            }

            return module.GetType(Identifier.For(@namespace), Identifier.For(name));
        }

        /// <summary>
        /// Returns <c>true</c> if the typeNode can be assigned to a variable of the specified type.
        /// </summary>
        /// <param name="typeNode">The typeNode to check.</param>
        /// <param name="type">The base type.</param>
        /// <returns><c>true</c> if the typeNode can be assigned to a variable of the specified type.</returns>
        /// <remarks>
        /// This basically checks to see if the typeNode is, or derives off of, the specified type.
        /// </remarks>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "Handled in inner method.")]
        public static bool IsAssignableTo(this TypeNode typeNode, Type type)
        {
            return TypeNodeHelper.IsAssignableTo(typeNode, type.FullName);
        }

        /// <summary>
        /// Returns <c>true</c> if the typeNode can be assigned to a variable of the specified type.
        /// </summary>
        /// <param name="typeNode">The typeNode to check.</param>
        /// <param name="typeFullName">The base type full name.</param>
        /// <returns><c>true</c> if the typeNode can be assigned to a variable of the specified type.</returns>
        /// <remarks>
        /// This basically checks to see if the typeNode is, or derives off of, the specified type.
        /// </remarks>
        public static bool IsAssignableTo(this TypeNode typeNode, string typeFullName)
        {
            if (typeNode == null)
            {
                throw new ArgumentNullException("typeNode");
            }

            while (typeNode != null)
            {
                if (typeNode.FullName == typeFullName)
                {
                    return true;
                }

                typeNode = typeNode.BaseType;
            }

            return false;
        }

        /// <summary>
        /// Returns <c>true</c> if the typeNode can be assigned to a variable of any of the specified types.
        /// </summary>
        /// <param name="typeNode">The typeNode to check.</param>
        /// <param name="typeFullNames">The base type full name.</param>
        /// <returns><c>true</c> if the typeNode can be assigned to a variable of any of the specified type.</returns>
        /// <remarks>
        /// This basically checks to see if the typeNode is, or derives off of, the specified type.
        /// </remarks>
        public static bool IsAssignableTo(this TypeNode typeNode, params string[] typeFullNames)
        {
            if (typeFullNames == null)
            {
                throw new ArgumentNullException("typeFullNames");
            }

            foreach (string typeFullName in typeFullNames)
            {
                if (TypeNodeHelper.IsAssignableTo(typeNode, typeFullName))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Determines the <see cref="TypeNode"/> is a code generated.
        /// </summary>
        /// <param name="typeNode">The type node.</param>
        /// <returns><c>true</c> if <see cref="TypeNode"/> is a code generated.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "By design.")]
        public static bool IsGeneratedCode(this TypeNode typeNode)
        {
            if (typeNode == null)
            {
                throw new ArgumentNullException("typeNode");
            }

            return
                (from attribute in typeNode.Attributes
                 where attribute.Type.FullName == "System.CodeDom.Compiler.GeneratedCodeAttribute"
                 select attribute)
                .Any();
        }
        #endregion Public Methods
    }
}